//============================================================================
// Name        : region.cpp
// Author      : dfoelber
//============================================================================

// INCLUDES //
#include "region.h"
#include "effect/erase_effect.h"
#include "effect/slide_effect.h"
#include "render_manager.h"
#include "image_manager.h"
#include <iostream>
//////////////

using namespace std;

/**
* Constructor
**/
Region::Region()
{
	active = NULL;
	hoverObject = NULL;
	dirty = false;
	visible = true;
}

/**
* Destructor
**/
Region::~Region()
{
	for( int i = 0; i < (int)objects.size(); i++ )
	{
		delete objects[i];
	}
}

/**
* Sets the region's boundary.
* 
* @param x The boundary's X coordinate
* @param y The boundary's Y coordinate
* @param width The boundary's width
* @param height The boundary's height
**/
void Region::SetBoundary( int x, int y, int width, int height )
{
	boundary.SetBoundary( x, y, width, height );
}

/**
* Checks if the region is dirty and requires a total update.
* 
* @return If the region is dirty
**/
bool Region::IsDirty()
{
	return dirty;
}

/**
* Sets the region to be dirty and requires a total update.
* 
* @param dirty If the region is dirty
**/
void Region::SetDirty( bool dirty )
{
	this->dirty = dirty;
}

/**
* Checks if the region is visible.
* 
* @return If the region is visible
**/
bool Region::IsVisible()
{
	return visible;
}

/**
* Sets the region's visibility.
* 
* @param visible If the region is visible
**/
void Region::SetVisible( bool visible )
{
	if( this->visible != visible )
		dirty = true;
	this->visible = visible;
}

/**
* Add an object, the region becomes the owner.
* 
* @param object The object to add
**/
void Region::AddObject( Object* object )
{
	objects.push_back( object );
}

/**
* Gets the active object.
* 
* @return The active object
**/
Object* Region::GetActiveObject()
{
	return active;
}

/**
* Sets the active object.
* 
* @param object The new active object
**/
void Region::SetActiveObject( Object* object )
{
	active = object;
}

/**
* Gets the object in the object list.
* 
* @param index The index in the object list
* @return The object in the specific position in the list
**/
Object* Region::GetObject( int index )
{
	return objects[ index ];
}

/**
* Notify the region of an event.
* 
* @param event The event that happened
* @return If the region used the event
**/
bool Region::ProcessEvent( SDL_Event event )
{
	if( active != NULL )
	{
		// Allow the active object to possibly use the input.
		if( active->GetPath()->Input( event ) )
		{
			return true;
		}
	}
	
	if( event.type == SDL_MOUSEMOTION )
	{
		mouseX = event.button.x;
		mouseY = event.button.y;
		
		CheckForHover();
	}
	
	return false;
}

/**
* Notifies of time passing.
* 
* @param The amount of time, in milliseconds, that passed since the last call
**/
void Region::TimePassed( int ms )
{
	// OK, alot happens here.
	for( int i = 0; i < (int)objects.size(); i++ )
	{
		// First, the object is moved via its path
		objects[i]->GetPath()->TimePassed( ms );
		
		// Next, the object is checked against the boundary
		boundary.Check( objects[i]->GetPath() );
		
		// Then, collision with other objects
		for( int o = 0; o < (int)objects.size(); o++ )
		{
			if( objects[o] != objects[i] )
			{
				bool contains = false;
				for( int a = 0; a < (int)objects[i]->GetAssociations().size(); a++ )
					if( objects[o] == objects[i]->GetAssociations()[a] )
						contains = true;
				
				if( !contains )
					CheckCollision( objects[i], objects[o] );
			}
		}
	}
	
	// Next advance all objects and update the dirty rectangles
	for( int i = 0; i < (int)objects.size(); i++ )
	{
		objects[i]->GetPath()->Advance();
	}
}

/**
* A chance for a region to add a custom rendering request. Anything in the object list will
* render automatically.
**/
void Region::Render()
{
}

/**
* Checks and handles the possible collision between objects.
* Will move the first if it collides with the second.
* 
* @param first The first object
* @param second The second object
**/
void Region::CheckCollision( Object* first, Object* second )
{
	Area fa = Area( first->GetPath()->GetX(), first->GetPath()->GetY(), first->GetPath()->GetWidth(), first->GetPath()->GetHeight() );
	Area sa = Area( second->GetPath()->GetX(), second->GetPath()->GetY(), second->GetPath()->GetWidth(), second->GetPath()->GetHeight() );
	bool hitTop = false, hitBottom = false, hitLeft = false, hitRight = false;
	
	// the first object hits the left side
	if( fa.GetX() + fa.GetWidth() + first->GetPath()->GetXDistance() > sa.GetX() + second->GetPath()->GetXDistance() && 
		fa.GetX() + fa.GetWidth() <= sa.GetX() &&
		(( fa.GetY() <= sa.GetY() && fa.GetY() + fa.GetHeight() >= sa.GetY() ) || 
		( fa.GetY() > sa.GetY() && sa.GetY() + sa.GetHeight() >= fa.GetY() )))
	{
		hitLeft = true;
	}
	// the first object hits the right side
	if( fa.GetX() + first->GetPath()->GetXDistance() < sa.GetX() + sa.GetWidth() + second->GetPath()->GetXDistance() && 
		fa.GetX() >= sa.GetX() + sa.GetWidth() &&
		(( fa.GetY() <= sa.GetY() && fa.GetY() + fa.GetHeight() >= sa.GetY() ) || 
		( fa.GetY() > sa.GetY() && sa.GetY() + sa.GetHeight() >= fa.GetY() )))
	{
		hitRight = true;
	}
	// the first object hits the top
	if(	fa.GetY() + fa.GetHeight() + first->GetPath()->GetYDistance() >= sa.GetY() + second->GetPath()->GetYDistance() && 
		fa.GetY() + fa.GetHeight() <= sa.GetY() &&
		(( fa.GetX() < sa.GetX() && fa.GetX() + fa.GetWidth() > sa.GetX() ) || 
		( fa.GetX() > sa.GetX() && sa.GetX() + sa.GetWidth() > fa.GetX() ) ||
		( sa.GetX() == fa.GetX() ) ))
	{
		hitTop = true;
	}
	// the first object hits the bottom
	if( fa.GetY() + first->GetPath()->GetYDistance() <= sa.GetY() + sa.GetHeight() + second->GetPath()->GetYDistance() && 
		fa.GetY() >= sa.GetY() + sa.GetHeight() &&
		(( fa.GetX() <= sa.GetX() && fa.GetX() + fa.GetWidth() >= sa.GetX() ) || 
		( fa.GetX() > sa.GetX() && sa.GetX() + sa.GetWidth() >= fa.GetX() )))
	{
		hitBottom = true;
	}
	
	// warn the object that it collided
	int side = 0;
	if( hitTop )
	{
		side |= Path::SIDE_BOTTOM;
	}
	if( hitLeft ) 
	{
		side |= Path::SIDE_RIGHT;
	}
	if( hitRight )
	{
		side |= Path::SIDE_LEFT;
	}
	if( hitBottom ) side |= Path::SIDE_TOP;
	
	if( side > 0 )
	{
		first->GetPath()->Collision( side, &sa );
	}
}

/**
* Gets the list of objects in the region.
* 
* Overridden from RegionEffectOwner.
* 
* @region Objects currently in the region
**/
vector<Object*>& Region::GetObjects()
{
	return objects;
}

/**
* Checks if the object passed is the active object or in the active object's association list.
* 
* Overridden from RegionEffectOwner.
* 
* @param object The object to check
* @return If the object is the active object or in the active object's association list
**/
bool Region::IsObjectActiveOrSimilar(Object* object)
{
	if( object == GetActiveObject() )
		return true;
	
	for( int i = 0; i < (int)GetActiveObject()->GetAssociations().size(); i++ )
	{
		if( object == GetActiveObject()->GetAssociations()[i] )
			return true;
	}
	
	return false;
}

/**
* Removes the given object from the region.
* 
* Overridden from RegionEffectOwner.
* 
* @param object The object to remove
* @param del If the object should be deleted
**/
void Region::RemoveObject( Object* object, bool del )
{
	for( int i = 0; i < (int)objects.size(); i++ )
	{
		if( object == objects[i] )
		{
			RenderManager::GetManager()->RemoveObject(object);
			
			objects.erase( objects.begin() + i );
		
			if( del )
			{
				delete object;
				object = NULL;
			}
			
			CheckForHover();
			
			return;
		}
	}
}

/**
* Gets the object, if any, that correspond to the X,Y coordinates.
* 
* Overridden from RegionEffectOwner.
* 
* @param x The X coordinate
* @param y The Y coordinate
* @return The associated object, or null if none correspond
**/
Object* Region::GetObjectByCoordinate(int x, int y)
{
	for( int i = 0; i < (int)objects.size(); i++ )
	{
		if( x >= objects[i]->GetPath()->GetX() && 
			x <= objects[i]->GetPath()->GetX() + objects[i]->GetPath()->GetWidth() &&
			y >= objects[i]->GetPath()->GetY() && 
			y <= objects[i]->GetPath()->GetY() + objects[i]->GetPath()->GetHeight() )
		{
			return objects[i];
		}
	}
	
	return NULL;
}

/**
* Calls for optional custom rendering.
* 
* @returns If the region requires to flipped entirely (as opposed to flipping just the objects)
**/
bool Region::CustomRender()
{
	return false;
}

/**
* Checks for a hoverring object.
**/
void Region::CheckForHover()
{
	Object* current = GetObjectByCoordinate( mouseX, mouseY );
	if( hoverObject != NULL && (current != hoverObject || 
								mouseX < hoverObject->GetPath()->GetX() ||
								mouseX > hoverObject->GetPath()->GetX() + hoverObject->GetPath()->GetWidth() ||
								mouseY < hoverObject->GetPath()->GetY() ||
								mouseY > hoverObject->GetPath()->GetY() + hoverObject->GetPath()->GetHeight()) )
	{
		hoverObject->SetHover( false );
	}
	hoverObject = current;
	if( hoverObject != NULL )
	{
		hoverObject->SetHover( true );
	}
}
